/**
* \file DGVTransform.cpp
*
* \brief This file defines the class that wraps all the transforms using threads.
* \author Shekhar S. Chandra, 2008
*
* This file is part of DGV Library.
*
* DGV is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* DGV is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with DGV.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "DGVTransform.h"

DGVTransform::DGVTransform(QObject *parent) : QThread(parent)
{
    reset();
}

DGVTransform::~DGVTransform()
{
    mutex.lock();

    abort = true;
    if(isRunning())
        condition.wakeOne();

    mutex.unlock();

    wait();
}

void DGVTransform::frt(DGVImage *image)
{
    QMutexLocker locker(&mutex);

    Array<imageType,2> bins; //!< Bin values for transforms
    resultImage = new DGVImage;

    if(image->data().cols() != image->data().rows())
    {
        cerr << "Transform ERROR: Data not correct size for FRT. Must be square. Size: " << image->data().rows() << "x" << image->data().cols() << endl;
        return;
    }

    transform = FRT;
    inData = image->dataPtr();
    outData = &bins;

    if (!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();

    resultImage->setPixmap(bins);

    emit resultAvailable(resultImage);
}

void DGVTransform::frt(Array<imageType,2> &image)
{
    QMutexLocker locker(&mutex);

    if(image.cols() != image.rows())
    {
        cerr << "Transform ERROR: Data not correct size for FRT. Must be square. Size: " << image.rows() << "x" << image.cols() << endl;
        return;
    }

    transform = FRT;
    inData = &image;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifrt(DGVImage *bins)
{
    ///\todo Broken
    Array<imageType,2> image; //!< Bin values for transforms

    transform = iFRT;
    RT.iFastRT(bins->data(),image);

    DGVImage *resultImage = new DGVImage;
    resultImage->setPixmap(image);

    //emit resultAvailable(resultImage);
}

void DGVTransform::ifrt(Array<imageType,2> &bins)
{
    QMutexLocker locker(&mutex);

    transform = iFRT;
    inData = &bins;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifrtUnscaled(DGVImage *bins)
{
    ///\todo Broken
    Array<imageType,2> image; //!< Bin values for transforms

    transform = iFRT_Unscaled;
    RT.iFastRT(bins->data(),image,false);

    DGVImage *resultImage = new DGVImage;
    resultImage->setPixmap(image);

    //emit resultAvailable(image);
}

void DGVTransform::ifrtUnscaled(Array<imageType,2> &bins)
{
    QMutexLocker locker(&mutex);

    transform = iFRT_Unscaled;
    inData = &bins;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft(Array<complex<imageType>,2> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(image.rows(),image.cols());

    transform = FFT;
    inComplexData = &image;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft(Array<complex<imageType>,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(kSpace.rows(),kSpace.cols());

    transform = iFFT;
    inComplexData = &kSpace;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft_real(Array<imageType,2> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(image.rows(),image.cols());

    transform = FFT_Real;
    inData = &image;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft_real(Array<complex<imageType>,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    frtSpace.resize(kSpace.rows(),kSpace.cols());

    transform = iFFT_Real;
    inComplexData = &kSpace;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft_1D(Array<complex<imageType>,1> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace_1D.resize(image.size());

    transform = FFT_1D;
    inComplexData_1D = &image;
    outComplexData_1D = &fftSpace_1D;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft_1D(Array<complex<imageType>,2> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(image.shape());
    fftSpace_1D.resize(image.cols());

    transform = FFT_1D_Batch;
    inComplexData = &image;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft_1D(Array<complex<imageType>,1> &kSpace)
{
    QMutexLocker locker(&mutex);

    fftSpace_1D.resize(kSpace.size());

    transform = iFFT_1D;
    inComplexData_1D = &kSpace;
    outComplexData_1D = &fftSpace_1D;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft_1D(Array<complex<imageType>,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(kSpace.shape());
    fftSpace_1D.resize(kSpace.cols());

    transform = iFFT_1D_Batch;
    inComplexData = &kSpace;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft_real_1D(Array<imageType,1> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace_1D.resize(image.size());

    transform = FFT_Real_1D;
    inData_1D = &image;
    outComplexData_1D = &fftSpace_1D;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::fft_real_1D(Array<imageType,2> &image)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(image.shape());
    fftSpace_1D.resize(image.cols());
    frtSpace_1D.resize(image.cols());

    transform = FFT_Real_1D_Batch;
    inData = &image;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft_real_1D(Array<complex<imageType>,1> &kSpace)
{
    QMutexLocker locker(&mutex);

    frtSpace_1D.resize(kSpace.size());

    transform = iFFT_Real_1D;
    inComplexData_1D = &kSpace;
    outData_1D = &frtSpace_1D;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::ifft_real_1D(Array<complex<imageType>,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    frtSpace.resize(kSpace.shape());
    frtSpace_1D.resize(kSpace.cols());
    fftSpace_1D.resize(kSpace.cols());

    transform = iFFT_Real_1D_Batch;
    inComplexData = &kSpace;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::radonSlices(Array<complex<imageType>,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    fftSpace.resize(kSpace.shape());

    transform = radonSlicing;
    inComplexData = &kSpace;
    outComplexData = &fftSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::radonSlices(Array<imageType,2> &kSpace)
{
    QMutexLocker locker(&mutex);

    frtSpace.resize(kSpace.shape());

    transform = radonSlicing_Real;
    inData = &kSpace;
    outData = &frtSpace;

    if(!isRunning())
        start();
    else
        condition.wakeOne();

    if(alwaysWait)
        wait();
}

void DGVTransform::reset()
{
    QMutexLocker locker(&mutex);

    alwaysWait = false;
    abort = false;
    consoleAssigned = false;
    transform = None;

    Fourier_1D.reset();
    Fourier.reset();

    RT.setInitializer(0);
    RT.init();
}

void DGVTransform::setConsole(DGVConsole *newConsole)
{
    QMutexLocker locker(&mutex);

    if(newConsole != NULL)
    {
        console = newConsole;
        consoleAssigned = true;
    }
}

void DGVTransform::run()
{
    QMutexLocker locker(&mutex);

    QString strDuration;
    int duration = 0;
    QTime *timer = new QTime;

    qApp->processEvents(); //!< Process Qt Events First

    forever
    {
        if(abort)
        {
            delete timer;
            return;
        }

        timer->start();
        switch(transform)
        {
            ///2D FFTs
            case FFT:
                Fourier.FFT(*inComplexData,*outComplexData);

                emit resultAvailable(*outComplexData, "FFT-" + name);
                break;
            case iFFT:
                Fourier.iFFT(*inComplexData,*outComplexData);

                emit resultAvailable(*outComplexData,"iFFT-" + name);
                break;
            case FFT_Real:
                Fourier.FFT_Real2Complex(*inData,*outComplexData);

                emit resultAvailable(*outComplexData,"Real FFT-" + name);
                break;
            case iFFT_Real:
                Fourier.FFT_Complex2Real(*inComplexData,*outData);

                emit resultAvailable(*outData,"Real iFFT-" + name);
                break;
            ///1D FFTs
            case FFT_1D:
                Fourier_1D.FFT(*inComplexData_1D,*outComplexData_1D);

                emit resultAvailable(*outComplexData_1D,"1D FFT-" + name);
                break;
            case FFT_1D_Batch:
                //inComplexData = &image;
                //outComplexData = &fftSpace;

                for(int j = 0; j < inComplexData->rows(); j ++)
                {
                    fftSpace_1D = (*inComplexData)(j,Range::all());

                    Fourier_1D.FFT(fftSpace_1D,fftSpace_1D);

                    fftSpace(j,Range::all()) = fftSpace_1D;
                }

                emit resultAvailable_1D(fftSpace,"1D FFTs-" + name);
                break;
            case iFFT_1D:
                Fourier_1D.iFFT(*inComplexData_1D,*outComplexData_1D);

                emit resultAvailable(*outComplexData_1D,"1D iFFT-" + name);
                break;
            case iFFT_1D_Batch:
                //inComplexData = &kSpace;
                //outComplexData = &fftSpace;

                for(int j = 0; j < inComplexData->rows(); j ++)
                {
                    fftSpace_1D = (*inComplexData)(j,Range::all());

                    Fourier_1D.iFFT(fftSpace_1D,fftSpace_1D);

                    fftSpace(j,Range::all()) = fftSpace_1D;
                }

                emit resultAvailable_1D(fftSpace,"1D iFFTs-" + name);
                break;
            case FFT_Real_1D:
                Fourier_1D.FFT_Real2Complex(*inData_1D,*outComplexData_1D);

                emit resultAvailable(*outComplexData_1D,"1D Real FFT-" + name);
                break;
            case FFT_Real_1D_Batch:
                //inData = &image;
                //outComplexData = &fftSpace;

                for(int j = 0; j < inData->rows(); j ++)
                {
                    frtSpace_1D = (*inData)(j,Range::all());

                    Fourier_1D.FFT_Real2Complex(frtSpace_1D,fftSpace_1D);

                    fftSpace(j,Range::all()) = fftSpace_1D;
                }

                emit resultAvailable_1D(fftSpace,"1D Real FFTs-" + name);
                break;
            case iFFT_Real_1D:
                Fourier_1D.FFT_Complex2Real(*inComplexData_1D,*outData_1D);

                emit resultAvailable(*outData_1D,"1D Real iFFT-" + name);
                break;
            case iFFT_Real_1D_Batch:
                //inComplexData = &kSpace;
                //outData = &frtSpace;

                for(int j = 0; j < inComplexData->rows(); j ++)
                {
                    fftSpace_1D = (*inComplexData)(j,Range::all());

                    Fourier_1D.FFT_Complex2Real(fftSpace_1D,frtSpace_1D);

                    frtSpace(j,Range::all()) = frtSpace_1D;
                }

                emit resultAvailable_1D(frtSpace,"1D Real iFFTs-" + name);
                break;
            ///Slicing
            case radonSlicing:
                RT.extractSlices(*inComplexData,*outComplexData);

                emit resultAvailable_1D(*outComplexData,"Radon Slices-" + name);
                break;
            case radonSlicing_Real:
                RT.extractSlices(*inData,*outData);

                emit resultAvailable_1D(*outData,"Radon Slices-" + name);
                break;
            ///FRTs
            case FRT:
                RT.FastRT(*inData,*outData);

                emit resultAvailable(*outData,"FRT-" + name);
                break;
            case iFRT:
                RT.iFastRT(*inData,*outData);

                emit resultAvailable(*outData,"iFRT-" + name);
                break;
            case iFRT_Unscaled:
                RT.iFastRT(*inData,*outData,false);

                emit resultAvailable(*outData,"iFRT Unscaled-" + name);
                break;
            case None:
                cerr << "No Transform Selected, Results may be unexpected!" << endl;
                return;
        }
        duration = timer->elapsed();

        if(consoleAssigned)
            console->printMessage("Transform took: " + strDuration.setNum(duration) + " ms");
        else
            cerr << ">| Solving Time Elapsed: " << duration << " ms" << endl;

        qApp->processEvents(); //!< Process Qt Events

        condition.wait(&mutex); ///Put to sleep
    }

    return;
}
